package dsk.demo.product.config;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import dsk.demo.common.utils.StreamUtils;
import dsk.demo.product.dao.GDayPriceDao;
import dsk.demo.product.dao.GGoodGupiaoDao;
import dsk.demo.product.entity.gupiao.GDayPrice;
import dsk.demo.product.entity.gupiao.GGoodGupiao;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.function.Function;

/**
 * 定期获取股票历史数据信息
 */
@Component
@Slf4j
public class GuPiaoJob {

    @Autowired
    private GDayPriceDao dayPriceDao;

    @Autowired
    private GGoodGupiaoDao goodGupiaoDao;

    @Resource(name = "MyExecutor")
    private ThreadPoolTaskExecutor myExecutor;

    private static String licence = "0D9C7E51-F014-4BAB-9C4F-55EDA398C834";

    // 获取股票价格
    private String getPriceUrl = "http://api.mairui.club/hszbc/fsjy/{gCode}/dq/{startTime}/{endTime}/{licence}";

    // 获取股票历史所有价格
    private String getPriceAllDataUrl = "http://api.mairui.club/hszbl/fsjy/{gCode}/dq/{licence}";

    // 获取股票列表  952555
    private String getGuPiaoListUrl = "http://api.mairui.club/hslt/list/{licence}";


    /**
     * 每日下午4点 收盘后获取所有的数据
     */
    @Scheduled(cron = "0 0 16 ? * *")
    public void getGuPiaoData() throws Exception {
        String format = StrUtil.format(getGuPiaoListUrl, Collections.singletonMap("licence", getRandomLicence()));
        String s = HttpUtil.get(format);
        JSONArray array = JSONUtil.parseArray(s);

        for (Object o : array) {
            JSONObject jo = (JSONObject) o;
            String gCode = jo.getStr("dm");
            String gName = jo.getStr("mc");
            Map<String, String> param = new HashMap<>();

            param.put("gCode", gCode);
            param.put("startTime", DateUtil.format(DateUtil.beginOfDay(new Date()), "yyyy-MM-dd"));
            param.put("endTime", DateUtil.format(DateUtil.beginOfDay(new Date()), "yyyy-MM-dd"));
            param.put("licence", getRandomLicence());
            String url = StrUtil.format(getPriceUrl, param);

            try {
                String s1 = HttpUtil.get(url);
                JSONArray dayPriceData = JSONUtil.parseArray(s1);
                List<GDayPrice> addList = new ArrayList<>();
                for (Object dayPriceDatum : dayPriceData) {
                    GDayPrice build = buildGuPiaoPrice(gCode, gName, (JSONObject) dayPriceDatum);
                    addList.add(build);
                }
                if (CollectionUtil.isNotEmpty(addList)) {
                    dayPriceDao.replaceBatch(addList);
                    myExecutor.execute(() -> {
                        // 计算均线
                        getLastDayAvg(gCode, new Date());
                    });
                }
            } catch (Exception e) {
            }
        }
    }


    public void GetAllGuPiao() {
        String format = StrUtil.format(getGuPiaoListUrl, Collections.singletonMap("licence", getRandomLicence()));
        String s = HttpUtil.get(format);
        JSONArray array = JSONUtil.parseArray(s);
        boolean flag = false;
        List<Map> mapList = dayPriceDao.selectAllGuPiao();
        for (Map jo : mapList) {
            String gCode = jo.get("gCode").toString();

            try {
                getAllGuPiaoData(gCode, null);
                log.info("新增股票:[{}]成功", gCode);
            } catch (Exception e) {
                log.error("新增股票:[{}]失败", gCode);
            }
        }
    }

    private GDayPrice buildGuPiaoPrice(String gCode, String gName, JSONObject dayPriceDatum) {
        JSONObject priceData = dayPriceDatum;
        GDayPrice build = GDayPrice.builder()
                .gCode(gCode)
                .gName(gName)
                .date(DateUtil.parse(priceData.getStr("d"), "yyyy-MM-dd")) // 日期
                .startPrice(new BigDecimal(priceData.getStr("o"))) // 开盘价
                .endPrice(new BigDecimal(priceData.getStr("c")))  // 收盘价
                .maxPrice(new BigDecimal(priceData.getStr("h"))) // 最高价
                .minPrice(new BigDecimal(priceData.getStr("l"))) // 最低价
                .turnover(new BigDecimal(priceData.getStr("v"))) // 成交量
                .turnoverAmount(new BigDecimal(priceData.getStr("e"))) // 成交额
                .zf(new BigDecimal(priceData.getStr("zf"))) // 振幅
                .zd(new BigDecimal(priceData.getStr("zd"))) // 涨跌幅
                .zde(new BigDecimal(priceData.getStr("zde"))) // 涨跌额
                .exchange(new BigDecimal(priceData.getStr("hs"))) // 换手率
                .build();
        return build;
    }

    /**
     * 每周日下午7点 回测涨跌幅
     */
    @Scheduled(cron = "0 0 17 ? * *")
    public void dealGoodGuPiaoZf() {
        List<GGoodGupiao> gGoodGupiaos3D = goodGupiaoDao.selectList(Wrappers.<GGoodGupiao>lambdaQuery()
                .isNull(GGoodGupiao::getNewPrice3)
                .or()
                .isNull(GGoodGupiao::getNewPrice5)
                .or()
                .isNull(GGoodGupiao::getNewPrice8)
                .orderByAsc(GGoodGupiao::getId)
        );
        // 计算3天后涨幅
        for (GGoodGupiao gGoodGupiao : gGoodGupiaos3D) {

            List<GDayPrice> gDayPrices = dayPriceDao.selectList(Wrappers.<GDayPrice>lambdaQuery()
                    .eq(GDayPrice::getGCode, gGoodGupiao.getGCode())
                    .gt(GDayPrice::getDate, gGoodGupiao.getDate())
                    .orderByAsc(GDayPrice::getDate)
                    .last("limit 8"));
            if (CollectionUtil.isEmpty(gDayPrices) || gDayPrices.size() < 3) {
                continue;
            }
            if (gDayPrices.size() >= 3) {
                GDayPrice gDayPrice = gDayPrices.get(2);
                gGoodGupiao.setNewPrice3(gDayPrice.getEndPrice());
            }
            if (gDayPrices.size() >= 5) {
                GDayPrice gDayPrice = gDayPrices.get(4);
                gGoodGupiao.setNewPrice5(gDayPrice.getEndPrice());
            }
            if (gDayPrices.size() >= 8) {
                GDayPrice gDayPrice = gDayPrices.get(7);
                gGoodGupiao.setNewPrice8(gDayPrice.getEndPrice());
            }
            goodGupiaoDao.updateById(gGoodGupiao);
        }
    }

    /**
     * 每日下午8点 推荐 55日均线回归股票
     */
    @Scheduled(cron = "0 0 18 ? * *")
    public void dealGoodGuPiao() {
        dealGoodGuPiaoDate55(new Date());
        dealGoodGuPiaoDate34(new Date());
        dealGoodGuPiaoDate21(new Date());
        dealGoodGuPiaoDate13(new Date());
    }
    // 回踩55线
    public void dealGoodGuPiaoDate55(Date date) {
        List<GDayPrice> gDayPrices = dayPriceDao.selectBackAvg55(date);
        if (CollectionUtil.isNotEmpty(gDayPrices)) {
            List<GGoodGupiao> gGoodGupiaos = BeanUtil.copyToList(gDayPrices, GGoodGupiao.class);
            Map<String, GDayPrice> gupiaoMap = StreamUtils.toMap(gDayPrices, GDayPrice::getGCode, Function.identity());
            for (GGoodGupiao gGoodGupiao : gGoodGupiaos) {
                gGoodGupiao.setId(null);
                gGoodGupiao.setReason("55日均线回踩");
                gGoodGupiao.setNowPrice(gupiaoMap.get(gGoodGupiao.getGCode()).getEndPrice());

                // 判断是否底部横盘翻倍回踩均线
                doubleBackKLine(gGoodGupiao);
                if (gGoodGupiao.getId() == null) {
                    goodGupiaoDao.insert(gGoodGupiao);
                }
            }
        }
    }

    // 回踩21线
    public void dealGoodGuPiaoDate21(Date date) {
        List<GDayPrice> gDayPrices = dayPriceDao.selectBackAvg21(date);
        if (CollectionUtil.isNotEmpty(gDayPrices)) {
            List<GGoodGupiao> gGoodGupiaos = BeanUtil.copyToList(gDayPrices, GGoodGupiao.class);
            Map<String, GDayPrice> gupiaoMap = StreamUtils.toMap(gDayPrices, GDayPrice::getGCode, Function.identity());
            for (GGoodGupiao gGoodGupiao : gGoodGupiaos) {
                gGoodGupiao.setId(null);
                gGoodGupiao.setReason("21日均线回踩");
                gGoodGupiao.setNowPrice(gupiaoMap.get(gGoodGupiao.getGCode()).getEndPrice());
                // 判断当前股票是否是新高底部横盘多年===========================================
                doubleBackKLine(gGoodGupiao);
            }
        }
    }

    // 回踩13线
    public void dealGoodGuPiaoDate13(Date date) {
        List<GDayPrice> gDayPrices = dayPriceDao.selectBackAvg13(date);
        if (CollectionUtil.isNotEmpty(gDayPrices)) {
            List<GGoodGupiao> gGoodGupiaos = BeanUtil.copyToList(gDayPrices, GGoodGupiao.class);
            Map<String, GDayPrice> gupiaoMap = StreamUtils.toMap(gDayPrices, GDayPrice::getGCode, Function.identity());
            for (GGoodGupiao gGoodGupiao : gGoodGupiaos) {
                gGoodGupiao.setId(null);
                gGoodGupiao.setReason("13日均线回踩");
                gGoodGupiao.setNowPrice(gupiaoMap.get(gGoodGupiao.getGCode()).getEndPrice());
                // 判断当前股票是否是新高底部横盘多年===========================================
                doubleBackKLine(gGoodGupiao);
            }
        }
    }

    // 回踩34线
    public void dealGoodGuPiaoDate34(Date date) {
        List<GDayPrice> gDayPrices = dayPriceDao.selectBackAvg34(date);
        if (CollectionUtil.isNotEmpty(gDayPrices)) {
            List<GGoodGupiao> gGoodGupiaos = BeanUtil.copyToList(gDayPrices, GGoodGupiao.class);
            Map<String, GDayPrice> gupiaoMap = StreamUtils.toMap(gDayPrices, GDayPrice::getGCode, Function.identity());
            for (GGoodGupiao gGoodGupiao : gGoodGupiaos) {
                gGoodGupiao.setId(null);
                gGoodGupiao.setReason("34日均线回踩");
                gGoodGupiao.setNowPrice(gupiaoMap.get(gGoodGupiao.getGCode()).getEndPrice());
                // 判断当前股票是否是新高底部横盘多年===========================================
                doubleBackKLine(gGoodGupiao);
            }
        }
    }

    // 判断是否翻倍回踩均线
    private void doubleBackKLine(GGoodGupiao gGoodGupiao) {
        // 判断是否最低点翻倍
        // 查询最高价格日的数据
        GDayPrice maxPriceDay = dayPriceDao.selectMaxData(gGoodGupiao.getGCode());

        // 查询自从新高后的最低价格.
        GDayPrice minPriceDay = dayPriceDao.selectMinDataAfterOneDay(gGoodGupiao.getGCode(), maxPriceDay.getDate(), gGoodGupiao.getDate());
        if (minPriceDay != null) {
            long l = DateUtil.betweenDay(minPriceDay.getDate(), maxPriceDay.getDate(), true);
            // 至少下跌3年 才见底
            if (l > 365 * 3) {
                // 顶部下跌80%, 才继续查看是否翻倍
                BigDecimal dingbuxiadie = minPriceDay.getMinPrice().divide(maxPriceDay.getMaxPrice(), RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP);
                if (dingbuxiadie.compareTo(new BigDecimal("0.2")) <= 0) {
                    // 查询自从新低后的最高价格
                    GDayPrice newMaxPriceDay = dayPriceDao.selectMaxDataAfterOneDay(gGoodGupiao.getGCode(), minPriceDay.getDate(), gGoodGupiao.getDate());
                    if (newMaxPriceDay != null) {
                        // 新低后上涨百分比
                        BigDecimal xindihoushangzhang = newMaxPriceDay.getMinPrice().divide(minPriceDay.getMaxPrice(), RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP);
                        if (xindihoushangzhang.compareTo(new BigDecimal("1.9")) >= 0) {
                            // 新低翻倍股票
                            // 且最新价格低于上涨价格
                            if (gGoodGupiao.getNowPrice().compareTo(newMaxPriceDay.getMaxPrice().multiply(new BigDecimal("0.68"))) < 0
                            && gGoodGupiao.getNowPrice().compareTo(minPriceDay.getMinPrice().multiply(new BigDecimal("1.7"))) < 0) {
                                gGoodGupiao.setReason(gGoodGupiao.getReason() + "; 底部翻倍股回踩均线");
                                goodGupiaoDao.insert(gGoodGupiao);
                            }
                        }
                    }
                }
            }

        }
    }

    private void getAllGuPiaoData(String gCode, String gName) {
//        Map<String, String> params = new HashMap<>();
//        params.put("licence", getRandomLicence());
//        params.put("gCode", gCode);
//        String format = StrUtil.format(getPriceAllDataUrl, params);
//        String s = HttpUtil.get(format);
//        JSONArray array = JSONUtil.parseArray(s);
        List<GDayPrice> list = dayPriceDao.selectList(Wrappers.<GDayPrice>lambdaQuery()
                .eq(GDayPrice::getGCode, gCode)
                .orderByDesc(GDayPrice::getDate)
        );

        // 排序, 日期倒序
        list.sort((a,b) -> b.getDate().compareTo(a.getDate()));

        for (int i=0;i< list.size() ;i++) {
            getAvgPriceByList(CollectionUtil.sub(list, i, i + 56));
        }
        for (List<GDayPrice> dayPrices : CollectionUtil.split(list, 1000)) {
            dayPriceDao.replaceBatch(dayPrices);
        }
    }

    // 根据一段倒序的股票数据, 计算均线

    /**
     * @param code 股票代码
     * @param date 日期
     */
    private void getLastDayAvg(String code, Date date) {
        // 查询55条日线数据
        List<GDayPrice> gDayPrices = dayPriceDao.selectList(Wrappers.<GDayPrice>lambdaQuery()
                .le(GDayPrice::getDate, date)
                .eq(GDayPrice::getGCode, code)
                .orderByDesc(GDayPrice::getDate)
                .last("limit 56")
        );
        if (CollectionUtil.isEmpty(gDayPrices)) {
            return;
        }
        // 计算均线
        GDayPrice gDayPrice = getAvgPriceByList(gDayPrices);
        dayPriceDao.updateById(gDayPrice);

    }

    private GDayPrice getAvgPriceByList(List<GDayPrice> gDayPrices) {
        GDayPrice gDayPrice = gDayPrices.get(0);

        if (gDayPrices.size() >= 55) {
            // 55线
            List<GDayPrice> sub = CollectionUtil.sub(gDayPrices, 0, 55);
            BigDecimal avg55 = sub.stream().map(GDayPrice::getEndPrice).reduce(BigDecimal.ZERO, BigDecimal::add).divide(new BigDecimal(55), RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP);
            gDayPrice.setAvg55(avg55);
        }

        if (gDayPrices.size() >= 34) {
            // 34线
            List<GDayPrice> sub = CollectionUtil.sub(gDayPrices, 0, 34);
            BigDecimal avg34 = sub.stream().map(GDayPrice::getEndPrice).reduce(BigDecimal.ZERO, BigDecimal::add).divide(new BigDecimal(34), RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP);
            gDayPrice.setAvg34(avg34);
        }

        if (gDayPrices.size() >= 21) {
            // 21线
            List<GDayPrice> sub = CollectionUtil.sub(gDayPrices, 0, 21);
            BigDecimal avg21 = sub.stream().map(GDayPrice::getEndPrice).reduce(BigDecimal.ZERO, BigDecimal::add).divide(new BigDecimal(21), RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP);
            gDayPrice.setAvg21(avg21);
        }

        if (gDayPrices.size() >= 13) {
            // 13线
            List<GDayPrice> sub = CollectionUtil.sub(gDayPrices, 0, 13);
            BigDecimal avg13 = sub.stream().map(GDayPrice::getEndPrice).reduce(BigDecimal.ZERO, BigDecimal::add).divide(new BigDecimal(13), RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP);
            gDayPrice.setAvg13(avg13);
        }
        if (gDayPrices.size() >= 8) {
            // 8线
            List<GDayPrice> sub = CollectionUtil.sub(gDayPrices, 0, 8);
            BigDecimal avg8 = sub.stream().map(GDayPrice::getEndPrice).reduce(BigDecimal.ZERO, BigDecimal::add).divide(new BigDecimal(8), RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP);
            gDayPrice.setAvg8(avg8);
        }
        if (gDayPrices.size() >= 5) {
            // 8线
            List<GDayPrice> sub = CollectionUtil.sub(gDayPrices, 0, 5);
            BigDecimal avg5 = sub.stream().map(GDayPrice::getEndPrice).reduce(BigDecimal.ZERO, BigDecimal::add).divide(new BigDecimal(5), RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP);
            gDayPrice.setAvg5(avg5);
        }
        return gDayPrice;
    }

    private String getRandomLicence() {
        return licence;
    }
}
